// **Github:** https://github.com/teambition/then.js
//
// **License:** MIT

/* global module, define, setImmediate, console */
;(function (root, factory) {
    'use strict'
  
    if (typeof module === 'object' && typeof module.exports === 'object') {
      module.exports = factory()
    } else if (typeof define === 'function' && define.amd) {
      define([], factory)
    } else {
      root.Thenjs = factory()
    }
  }(typeof window === 'object' ? window : this, function () {
    'use strict'
  
    var maxTickDepth = 100
    var toString = Object.prototype.toString
    var hasOwnProperty = Object.prototype.hasOwnProperty
    var nextTick = typeof setImmediate === 'function' ? setImmediate : function (fn) {
      setTimeout(fn, 0)
    }
    var isArray = Array.isArray || function (obj) {
      return toString.call(obj) === '[object Array]'
    }
  
    // 将 `arguments` 转成数组，效率比 `[].slice.call` 高很多
    function slice (args, start) {
      start = start || 0
      if (start >= args.length) return []
      var len = args.length
      var ret = Array(len - start)
      while (len-- > start) ret[len - start] = args[len]
      return ret
    }
  
    function map (array, iterator) {
      var res = []
      for (var i = 0, len = array.length; i < len; i++) res.push(iterator(array[i], i, array))
      return res
    }
  
    // 同步执行函数，同时捕捉异常
    function carry (errorHandler, fn) {
      try {
        fn.apply(null, slice(arguments, 2))
      } catch (error) {
        errorHandler(error)
      }
    }
  
    // 异步执行函数，同时捕捉异常
    function defer (errorHandler, fn) {
      var args = arguments
      nextTick(function () {
        carry.apply(null, args)
      })
    }
  
    function toThunk (object) {
      if (object == null) return object
      if (typeof object.toThunk === 'function') return object.toThunk()
      if (typeof object.then === 'function') {
        return function (callback) {
          object.then(function (res) {
            callback(null, res)
          }, callback)
        }
      } else return object
    }
  
    function arrayToTasks (array, iterator) {
      return map(array, function (value, index, list) {
        return function (done) {
          iterator(done, value, index, list)
        }
      })
    }
  
    // ## **Thenjs** 主函数
    function Thenjs (start, debug) {
      var self = this
      var cont
      if (start instanceof Thenjs) return start
      if (!(self instanceof Thenjs)) return new Thenjs(start, debug)
      self._chain = 0
      self._success = self._parallel = self._series = null
      self._finally = self._error = self._result = self._nextThen = null
      if (!arguments.length) return self
  
      cont = genContinuation(self, debug)
      start = toThunk(start)
      if (start === void 0) cont()
      else if (typeof start === 'function') defer(cont, start, cont)
      else cont(null, start)
    }
  
    Thenjs.defer = defer
  
    Thenjs.parallel = function (tasks, debug) {
      return new Thenjs(function (cont) {
        carry(cont, parallel, cont, tasks)
      }, debug)
    }
  
    Thenjs.series = function (tasks, debug) {
      return new Thenjs(function (cont) {
        carry(cont, series, cont, tasks)
      }, debug)
    }
  
    Thenjs.each = function (array, iterator, debug) {
      return new Thenjs(function (cont) {
        carry(cont, parallel, cont, arrayToTasks(array, iterator))
      }, debug)
    }
  
    Thenjs.eachSeries = function (array, iterator, debug) {
      return new Thenjs(function (cont) {
        carry(cont, series, cont, arrayToTasks(array, iterator))
      }, debug)
    }
  
    Thenjs.parallelLimit = function (tasks, limit, debug) {
      return new Thenjs(function (cont) {
        parallelLimit(cont, tasks, limit)
      }, debug)
    }
  
    Thenjs.eachLimit = function (array, iterator, limit, debug) {
      return new Thenjs(function (cont) {
        parallelLimit(cont, arrayToTasks(array, iterator), limit)
      }, debug)
    }
  
    Thenjs.nextTick = function (fn) {
      var args = slice(arguments, 1)
      nextTick(function () {
        fn.apply(null, args)
      })
    }
  
    // 全局 error 监听
    Thenjs.onerror = function (error) {
      console.error('Thenjs caught error: ', error)
      throw error
    }
  
    var proto = Thenjs.prototype
    // **Thenjs** 对象上的 **finally** 方法
    proto.fin = proto['finally'] = function (finallyHandler) {
      return thenFactory(function (cont, self) {
        self._finally = wrapTaskHandler(cont, finallyHandler)
      }, this)
    }
  
    // **Thenjs** 对象上的 **then** 方法
    proto.then = function (successHandler, errorHandler) {
      return thenFactory(function (cont, self) {
        if (successHandler) self._success = wrapTaskHandler(cont, successHandler)
        if (errorHandler) self._error = wrapTaskHandler(cont, errorHandler)
      }, this)
    }
  
    // **Thenjs** 对象上的 **fail** 方法
    proto.fail = proto['catch'] = function (errorHandler) {
      return thenFactory(function (cont, self) {
        self._error = wrapTaskHandler(cont, errorHandler)
        // 对于链上的 fail 方法，如果无 error ，则穿透该链，将结果输入下一链
        self._success = function () {
          var args = slice(arguments)
          args.unshift(null)
          cont.apply(null, args)
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **parallel** 方法
    proto.parallel = function (tasks) {
      return thenFactory(function (cont, self) {
        self._parallel = function (_tasks) {
          parallel(cont, tasks || _tasks)
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **series** 方法
    proto.series = function (tasks) {
      return thenFactory(function (cont, self) {
        self._series = function (_tasks) {
          series(cont, tasks || _tasks)
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **each** 方法
    proto.each = function (array, iterator) {
      return thenFactory(function (cont, self) {
        self._parallel = function (_array, _iterator) {
          // 优先使用定义的参数，如果没有定义参数，则从上一链结果从获取
          // `_array`, `_iterator` 来自于上一链的 **cont**，下同
          parallel(cont, arrayToTasks(array || _array, iterator || _iterator))
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **eachSeries** 方法
    proto.eachSeries = function (array, iterator) {
      return thenFactory(function (cont, self) {
        self._series = function (_array, _iterator) {
          series(cont, arrayToTasks(array || _array, iterator || _iterator))
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **parallelLimit** 方法
    proto.parallelLimit = function (tasks, limit) {
      return thenFactory(function (cont, self) {
        self._parallel = function (_tasks) {
          parallelLimit(cont, tasks || _tasks, limit)
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **eachLimit** 方法
    proto.eachLimit = function (array, iterator, limit) {
      return thenFactory(function (cont, self) {
        self._series = function (_array, _iterator) {
          parallelLimit(cont, arrayToTasks(array || _array, iterator || _iterator), limit)
        }
      }, this)
    }
  
    // **Thenjs** 对象上的 **toThunk** 方法
    proto.toThunk = function () {
      var self = this
      return function (callback) {
        if (self._result) {
          callback.apply(null, self._result)
          self._result = false
        } else if (self._result !== false) {
          self._finally = self._error = callback
        }
      }
    }
  
    // util.inspect() implementation
    proto.inspect = function () {
      var obj = {}
      for (var key in this) {
        if (!hasOwnProperty.call(this, key)) continue
        obj[key] = key === '_nextThen' ? (this[key] && this[key]._chain) : this[key]
      }
      return obj
    }
  
    // 核心 **continuation** 方法
    // **continuation** 收集任务结果，触发下一个链，它被注入各个 handler
    // 其参数采用 **node.js** 的 **callback** 形式：(error, arg1, arg2, ...)
    function continuation () {
      var self = this
      var args = slice(arguments)
  
      // then链上的结果已经处理，若重复执行 cont 则直接跳过；
      if (self._result === false) return
      // 第一次进入 continuation，若为 debug 模式则执行，对于同一结果保证 debug 只执行一次；
      if (!self._result && self._chain) {
        self.debug.apply(self, ['\nChain ' + self._chain + ': '].concat(slice(args)))
      }
      // 标记已进入 continuation 处理
      self._result = false
  
      carry(function (err) {
        if (err === args[0]) continuationError(self, err)
        else continuation.call(self._nextThen, err)
      }, continuationExec, self, args)
    }
  
    function continuationExec (ctx, args) {
      if (args[0] == null) args[0] = null
      else {
        args = [args[0]]
        if (!ctx._finally) throw args[0]
      }
      if (ctx._finally) return ctx._finally.apply(null, args)
      var success = ctx._success || ctx._parallel || ctx._series
      if (success) return success.apply(null, slice(args, 1))
      // 对于正确结果，**Thenjs** 链上没有相应 handler 处理，则在 **Thenjs** 链上保存结果，等待下一次处理。
      ctx._result = args
    }
  
    function continuationError (ctx, err) {
      var _nextThen = ctx
      var errorHandler = ctx._error || ctx._finally
  
      // 获取后链的 error handler
      while (!errorHandler && _nextThen._nextThen) {
        _nextThen = _nextThen._nextThen
        errorHandler = _nextThen._error || _nextThen._finally
      }
  
      if (errorHandler) {
        return carry(function (_err) {
          // errorHandler 存在则 _nextThen._nextThen 必然存在
          continuation.call(_nextThen._nextThen, _err)
        }, errorHandler, err)
      }
      // 如果定义了全局 **onerror**，则用它处理
      if (Thenjs.onerror) return Thenjs.onerror(err)
      // 对于 error，如果没有任何 handler 处理，则保存到链上最后一个 **Thenjs** 对象，等待下一次处理。
      while (_nextThen._nextThen) _nextThen = _nextThen._nextThen
      _nextThen._result = [err]
    }
  
    function genContinuation (ctx, debug) {
      function cont () {
        return continuation.apply(ctx, arguments)
      }
      // 标记 cont，cont 作为 handler 时不会被注入 cont，见 `wrapTaskHandler`
      cont._isCont = true
      // 设置并开启 debug 模式
      if (debug) {
        proto.debug = typeof debug === 'function' ? debug : defaultDebug
        ctx._chain = 1
      }
      return cont
    }
  
    // 注入 cont，执行 fn，并返回新的 **Thenjs** 对象
    function thenFactory (fn, ctx, debug) {
      var nextThen = new Thenjs()
      var cont = genContinuation(nextThen, debug)
  
      // 注入 cont，初始化 handler
      fn(cont, ctx)
      if (!ctx) return nextThen
      ctx._nextThen = nextThen
      if (ctx._chain) nextThen._chain = ctx._chain + 1
      // 检查上一链的结果是否处理，未处理则处理，用于续接 **Thenjs** 链
      if (ctx._result) {
        nextTick(function () {
          continuation.apply(ctx, ctx._result)
        })
      }
      return nextThen
    }
  
    // 封装 handler，`_isCont` 判定 handler 是不是 `cont` ，不是则将 `cont` 注入成第一个参数
    function wrapTaskHandler (cont, handler) {
      return handler._isCont ? handler : function () {
        var args = slice(arguments)
        args.unshift(cont)
        handler.apply(null, args)
      }
    }
  
    // ## **parallel** 函数
    // 并行执行一组 `task` 任务，`cont` 处理最后结果
    function parallel (cont, tasks) {
      if (!isArray(tasks)) return cont(errorify(tasks, 'parallel'))
      var pending = tasks.length
      var result = []
  
      if (pending <= 0) return cont(null, result)
      for (var i = 0, len = pending; i < len; i++) tasks[i](genNext(i))
  
      function genNext (index) {
        function next (error, value) {
          if (pending <= 0) return
          if (error != null) return (pending = 0, cont(error))
          result[index] = value
          return !--pending && cont(null, result)
        }
        next._isCont = true
        return next
      }
    }
  
    // ## **series** 函数
    // 串行执行一组 `array` 任务，`cont` 处理最后结果
    function series (cont, tasks) {
      if (!isArray(tasks)) return cont(errorify(tasks, 'series'))
      var i = 0
      var end = tasks.length - 1
      var run
      var result = []
      var stack = maxTickDepth
  
      if (end < 0) return cont(null, result)
      next._isCont = true
      tasks[0](next)
  
      function next (error, value) {
        if (error != null) return cont(error)
        result[i] = value
        if (++i > end) return cont(null, result)
        // 先同步执行，嵌套达到 maxTickDepth 时转成一次异步执行
        run = --stack > 0 ? carry : (stack = maxTickDepth, defer)
        run(cont, tasks[i], next)
      }
    }
  
    function parallelLimit (cont, tasks, limit) {
      var index = 0
      var pending = 0
      var len = tasks.length
      var queue = []
      var finished = false
  
      limit = limit >= 1 ? Math.floor(limit) : Number.MAX_VALUE
      do { checkNext() } while (index < len && pending < limit)
      function checkNext () {
        if (finished) return
        if (index >= len) {
          finished = true
          return parallel(cont, queue)
        }
        if (pending >= limit) return
        pending++
        queue.push(evalTask())
      }
  
      function evalTask () {
        return new Thenjs(tasks[index++]).fin(function (next, err, res) {
          if (err != null) {
            finished = true
            return cont(err)
          }
          pending--
          checkNext()
          next(null, res)
        }).toThunk()
      }
    }
  
    // 默认的 `debug` 方法
    function defaultDebug () {
      console.log.apply(console, arguments)
    }
  
    // 参数不合法时生成相应的错误
    function errorify (obj, method) {
      return new Error('The argument ' + (obj && obj.toString()) + ' in "' + method + '" is not Array!')
    }
  
    Thenjs.NAME = 'Thenjs'
    Thenjs.VERSION = '2.0.3'
    return Thenjs
  }))